﻿/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.UI
{

	public abstract class Selector : Element
	{
		public Property<Vector2> LayoutDisplacement;
		public Property<int> LayoutCount;

		protected Selector( UiContext owner )
			: base( owner )
		{
			LayoutDisplacement.SetSimpleValue( new Vector2( 64, 0 ) );
			LayoutCount.SetSimpleValue( 1 );

			itemGroup = CreateItemGroup();
		}

		protected abstract int ItemCount { get; }
		protected abstract void TraverseItemToRender( int index, ITraversalContext context );

		#region ItemGroup

		protected class ItemGroup : Element
		{
			protected internal ItemGroup( Selector owner )
				: base( owner.UiContext )
			{
				this.Owner = owner;

				this.Origin.SetDynamicValue( ComputeOrigin );
				this.Opacity.SetDynamicValue( ComputeOpacity );
			}

			protected virtual Vector2 ComputeOrigin()
			{
				float scrollOffset = Owner.scrollOffset.Evaluate( UiContext.Now );
				int scrollIndex = (int)scrollOffset;
				float t = scrollOffset - scrollIndex;
				int screenIndex = CurrentIndex - scrollIndex;

				Vector2 layoutDisp = Owner.LayoutDisplacement.Evaluate();

				return layoutDisp * (screenIndex - t);
			}

			protected virtual float ComputeOpacity()
			{
				float scrollOffset = Owner.scrollOffset.Evaluate( UiContext.Now );
				int scrollIndex = (int)scrollOffset;
				float t = scrollOffset - scrollIndex;
				int screenIndex = CurrentIndex - scrollIndex;

				if( t == 0 )
					return 1.0F;

				if( screenIndex == 0 )
					return 1.0F - t;

				if( screenIndex == Owner.LayoutCount.Evaluate() )
					return t;

				return 1.0F;
			}

			protected Selector Owner { get; private set; }
			protected internal int CurrentIndex
			{
				get { return Boxes.UnboxOrDfault( GetRoutingMeta(), -1 ); }
			}

			protected internal override void TraverseToRender( ITraversalContext context )
			{
				Owner.TraverseItemToRender( CurrentIndex, context );
			}
		}

		protected virtual ItemGroup CreateItemGroup()
		{
			return new ItemGroup( this );
		}

		private ItemGroup itemGroup;
		public Element GroupElement { get { return itemGroup; } }

		#endregion

		#region Scrolling

		public void ScrollTo( int index, bool snap )
		{
			index = Helpers.Clamp( index, 0, Math.Max( ItemCount - LayoutCount.Evaluate(), 0 ) );
			
			scrollOffset.Set( index, UiContext.Now,
				snap ? TimeSpan.Zero : RollingValue.DefaultRollTime );

			OnScrolled( EventArgs.Empty );
		}

		public void ScrollIntoView( int index, bool snap )
		{
			int itemCount = ItemCount;
			int layoutCount = LayoutCount.Evaluate();

			index = Helpers.Clamp( index, 0, itemCount );

			int scrollOfs = (int)scrollOffset.TargetValue;

			if( index < scrollOfs )
			{
				ScrollTo( index, snap );
			}
			else if( index > scrollOfs + layoutCount - 1 )
			{
				ScrollTo( index - layoutCount + 1, snap );
			}
		}

		public event EventHandler Scrolled;
		protected virtual void OnScrolled( EventArgs e )
		{
			if( Scrolled != null )
				Scrolled( this, e );
		}

		private RollingValue scrollOffset;

		#endregion

		protected internal override void TraverseToRender( ITraversalContext context )
		{
			float scrollOffset = this.scrollOffset.Evaluate( UiContext.Now );
			int scrollIndex = (int)scrollOffset;
			float t = scrollOffset - scrollIndex;

			int count = ItemCount;
			int layoutCount = LayoutCount.Evaluate();

			int min, max;

			if( t == 0 )
			{
				min = scrollIndex;
				max = min + layoutCount;
			}
			else
			{
				min = scrollIndex;
				max = min + layoutCount + 1;
			}

			max = Helpers.Clamp( max, min, count );

			for( int i = min; i < max; i++ )
				context.Traverse( itemGroup, Boxes.Int32s[i] );
		}

		public void PushGroupElement( int index, MutableRoutingPath path )
		{
			int count = ItemCount;
			index = Helpers.Clamp( index, 0, count - 1 );

			path.Push( itemGroup, Boxes.Int32s[index] );
		}

		/// <summary>
		/// Gets the index associated with the elements
		/// currently being examined on the stack.
		/// </summary>
		/// <returns>
		/// The index associated with the element currently
		/// on the stack, or -1 if the element isn't on the stack.
		/// </returns>
		/// <remarks>
		/// This method is intended to be called from the
		/// displayed children of this selector.
		/// 
		/// The same rules apply to this as apply to
		/// <see cref="GetRoutingMeta()"/>.
		/// </remarks>
		public int FetchTraversalIndex()
		{
			return Boxes.UnboxOrDfault( itemGroup.GetRoutingMeta(), -1 );
		}

		protected internal override void OnHandleEvent( IEventRoutingContext context )
		{
			
		}
	}

	public class Pager : Selector
	{
		public Pager( UiContext context )
			: base( context )
		{
			Pages = new List<Element>();
		}

		public List<Element> Pages { get; private set; }

		protected override int ItemCount
		{
			get { return Pages.Count; }
		}

		protected override void TraverseItemToRender( int index, ITraversalContext context )
		{
			context.Traverse( Pages[index] );
		}

		/// <summary>
		/// Pushes the group element and the page element for the given index.
		/// </summary>
		/// <param name="index">
		/// The index of the page to push.
		/// </param>
		/// <param name="path">
		/// The <see cref="MutableRoutingPath"/> to push the values onto.
		/// </param>
		public void PushPage( int index, MutableRoutingPath path )
		{
			if( Pages.Count == 0 )
				return;

			index = Helpers.Clamp( index, 0, Pages.Count - 1 );

			path.Push( GroupElement, Boxes.Int32s[index] );
			path.Push( Pages[index] );
		}
	}

	public class List : Selector
	{
		public List( UiContext context )
			: base( context )
		{
		}

		public Property<IList> Items;

		public Element Template { get; set; }

		protected override int ItemCount
		{
			get
			{
				IList items = Items.Evaluate();
				return items != null ? items.Count : 0;
			}
		}

		protected override void TraverseItemToRender( int index, ITraversalContext context )
		{
			if( Template == null )
				return;

			IList items = Items.Evaluate();
			if( items != null )
				context.Traverse( Template, items[index] );
		}

		/// <summary>
		/// Pushes the group element and the template for the given item
		/// onto the given routing path instance.
		/// </summary>
		/// <param name="index">
		/// The index of the item to push.
		/// </param>
		/// <param name="path">
		/// The <see cref="MutableRoutingPath"/> to push the values onto.
		/// </param>
		public void PushTemplate( int index, MutableRoutingPath path )
		{
			if( Template == null )
				return;

			UiContext.PushTemporaryRoute( path );
			
			IList items = Items.Evaluate();
			
			UiContext.PopTemporaryRoute();

			if( items != null && items.Count > 0 )
			{
				index = Helpers.Clamp( index, 0, items.Count - 1 );

				path.Push( GroupElement, Boxes.Int32s[index] );
				path.Push( Template, items[index] );
			}
		}
	}

	public static class SelectorHelpers
	{
		/// <summary>
		/// Pushes a <see cref="Pager"/>, its group element,
		/// and the page at the given index.
		/// </summary>
		/// <param name="path"></param>
		/// <param name="pager"></param>
		/// <param name="index"></param>
		public static void PushSelector( this MutableRoutingPath path, Pager pager, int index )
		{
			path.Push( pager );
			pager.PushPage( index, path );
		}

		/// <summary>
		/// Pushes a <see cref="List"/>, its group element,
		/// and the template at the given index.
		/// </summary>
		/// <param name="path"></param>
		/// <param name="list"></param>
		/// <param name="index"></param>
		public static void PushSelector( this MutableRoutingPath path, List list, int index )
		{
			path.Push( list );
			list.PushTemplate( index, path );
		}

		public static void PushSelector( this MutableRoutingPath path, Selector selector, int index )
		{
			if( selector is Pager )
				PushSelector( path, selector as Pager, index );
			else if( selector is List )
				PushSelector( path, selector as List, index );
			else
				throw new ArgumentException();
		}
	}

}